﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using CoreEx.Common.Proxy;
using LinFu.IoC.Configuration;
using LinFu.Reflection.Emit;
using Mono.Cecil;
using Mono.Cecil.Cil;
using CoreEx.Common.Extensions;
using System.Linq;
namespace CoreEx.Common.Implementation.Proxy
{
    [Implements(typeof (IInitializationInfoEmitter))]
    public class InitializationInfoEmitter : IInitializationInfoEmitter
    {
        /// <summary>
        /// A reference to the <see cref="ConstructorInfo"/> that is used to 
        /// create an <see cref="InitalizationInfo"/> instance.
        /// </summary>
        private static readonly ConstructorInfo _initializationInfoConstructor;

        static InitializationInfoEmitter()
        {
            //Create an arrays of types to used when searching for the 
            //InitalizationInfo constructor
            var types = new[]
                            {
                                typeof (object),
                                typeof (ConstructorInfo),
                                typeof (StackTrace),
                                typeof (Type),
                                typeof (Type[])
                            };

            //Get a reference to the constructor of the InitalizationInfo class
            _initializationInfoConstructor = typeof (InitalizationInfo).GetConstructor(types);
        }

        #region IInitializationInfoEmitter Members

        public VariableDefinition Emit(MethodDefinition targetMethod, Type baseType, IEnumerable<Type> interfaces)
        {
            
            ModuleDefinition module = targetMethod.DeclaringType.Module;

            var initializationInfo = targetMethod.AddLocal(typeof (IInitalizationInfo));

            CilWorker cilWorker = targetMethod.GetILGenerator();

            //Push the proxy instance onto the stack
            cilWorker.Emit(OpCodes.Ldarg_0);

            //Push the intercepted constructor onto the stack 
            cilWorker.PushMethod(targetMethod, module);

            // Push the stack trace onto the stack
            cilWorker.PushStackTrace(module);
                                
            //Push the base type onto the stack
            cilWorker.PushType(module,targetMethod,baseType);
            

            //Push the interfaces onto the stack
            cilWorker.PushTypes(module,targetMethod,interfaces.ToArray());

            //Create a new instance of the InitalizationInfo class and save it back to the initializationInfo variable
            MethodReference infoConstructor = module.Import(_initializationInfoConstructor);
            cilWorker.Emit(OpCodes.Newobj, infoConstructor);
            cilWorker.Emit(OpCodes.Stloc, initializationInfo);
            return initializationInfo;
        }

        #endregion
    }
}